﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using System.ComponentModel;

namespace EzDesk.EventAggregation
{
    /// <summary>
    /// Specifies on which thread a <see cref="CompositePresentationEvent{TPayload}"/> subscriber will be called.
    /// </summary>
    public enum ThreadOption
    {
        /// <summary>
        /// The call is done on the same thread on which the <see cref="CompositePresentationEvent{TPayload}"/> was published.
        /// </summary>
        PublisherThread,

        /// <summary>
        /// The call is done on the UI thread.
        /// </summary>
        UIThread,

        /// <summary>
        /// The call is done asynchronously on a background thread.
        /// </summary>
        BackgroundThread
    }

    public class EventSubscriptor<TPayload>
    {
        public Action<TPayload> Method
        { get; set; }
        public ThreadOption ThreadOption { get; set; }
        public void Execute(TPayload data)
        {
            ThreadExecution.Execute<TPayload>(Method, data, ThreadOption);
        }
    }
    public class ThreadExecution
    {
        public static void Execute<T>(Action<T> method, T data, ThreadOption threadOption)
        {
            switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    CommonExecute(method, data);
                    break;
                case ThreadOption.BackgroundThread:
                    BackgroundExecute(method, data);
                    break;
                case ThreadOption.UIThread:
                    UIThreadExecute(method,data);
                    break;
            }
        }

        public static void CommonExecute<T>(Action<T> method, T data)
        {
            method(data);
        }
        public static void BackgroundExecute<T>(Action<T> method, T data)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (sender, e) => method((T)e.Argument);

            // handle worker.RunWorkerCompleted and log exceptions?
            worker.RunWorkerAsync(data);
        }
        public static void UIThreadExecute<T>(Action<T> method, T data)
        {
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, data);
            }
        }
    }

    public class CompositeEvent<TPayload>
    {

        //Leave the code here that Used to @Refactory
        //private static CompositeEvent<TPayload> _instance;
        //public static CompositeEvent<TPayload> Instance
        //{
        //    get
        //    {
        //        if (_instance == null)
        //        {
        //            _instance = EventAggregator<CompositeEvent<TPayload>>.GetEvent();
        //        }
        //        return _instance;
        //    }
        //}

        private readonly List<EventSubscriptor<TPayload>> _subscriptions = new List<EventSubscriptor<TPayload>>();

        /// <summary>
        /// Gets the list of current subscriptions.
        /// </summary>
        /// <value>The current subscribers.</value>
        protected ICollection<EventSubscriptor<TPayload>> Subscriptions
        {
            get { return _subscriptions; }
        }

        public virtual void Publish(TPayload payload)
        {
            foreach (var subscribe in Subscriptions)
            {
                subscribe.Execute(payload);
            }
        }


        public EventSubscriptor<TPayload> Subscribe(Action<TPayload> method)
        {
            var subscribe = new EventSubscriptor<TPayload>
            {
                Method = method,
                ThreadOption = ThreadOption.UIThread
            };
            lock (Subscriptions)
            {
                _subscriptions.Add(subscribe);
            }
            return subscribe;
        }


        public EventSubscriptor<TPayload> Subscribe(Action<TPayload> method, ThreadOption threadOption)
        {
            var subscribe = new EventSubscriptor<TPayload>
            {
                Method = method,
                ThreadOption = threadOption
            };
            lock (Subscriptions)
            {
                _subscriptions.Add(subscribe);
            }
            return subscribe;
        }

        public void Unsubscribe(EventSubscriptor<TPayload> subscribe)
        {
            lock (Subscriptions)
            {
                 _subscriptions.Remove(subscribe);
            }
        }

        public void Unsubscribe(Action<TPayload> method)
        {
            lock (Subscriptions)
            {
                var subscribe = _subscriptions.FirstOrDefault(sub => sub.Method == method);
                _subscriptions.Remove(subscribe);
            }
        }
    }

    public static class EventAggregator<TEvent> where TEvent : new()
    {
        private static object _objLock = new object();
        private static TEvent _instance;

        private static TEvent Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new TEvent();
                        }
                    }
                }
                return _instance;
            }
        }

        public static TEvent GetEvent()
        {
            if (_instance == null)
            {
                lock (_objLock)
                {
                    if (_instance == null)
                    {
                        _instance = new TEvent();
                    }
                }
            }
            return _instance;
        }
    }
}
